package com.zhang.gateway.filter.global;

import com.alibaba.fastjson.JSON;
import com.zhang.gateway.common.model.InvalidTokenException;
import com.zhang.gateway.common.model.constant.HeaderConstant;
import com.zhang.gateway.properties.BaseProperties;
import com.zhang.gateway.remote.AuthRemote;
import com.zhang.infrastructure.api.model.authority.GetUserReqDTO;
import com.zhang.infrastructure.api.model.authority.UserResDTO;
import com.zhang.infrastructure.model.KingHoodResponseVO;
import com.zhang.infrastructure.model.constant.redis.RedisConstant;
import com.zhang.infrastructure.model.token.UserDTO;
import com.zhang.redis.util.RedisManager;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.annotation.Order;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.ThreadLocalRandom;

/**
 * @Copyright 深圳金雅福控股集团有限公司
 * @Author: zhangfanjun
 * @Date 2021/11/17
 * @Version: 1.0
 */
@RequiredArgsConstructor
@Slf4j
@Component
@Order(value = 1)
public class RequestHeaderSetFilter implements GlobalFilter {

    private final BaseProperties baseProperties;
    private final AuthRemote authRemote;
    private final RedisManager<String> redisManager;
    private final boolean reactor = true;

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        String requestUrl = exchange.getRequest().getPath().value();
        log.info("{},封装请求头",requestUrl);
        //判断是否在白名单的范围
        boolean white = false;
        List<String> ignoreUriList = baseProperties.getIgnoreUriList();
        if (!CollectionUtils.isEmpty(ignoreUriList) && checkUrl(ignoreUriList, requestUrl)) {
            log.info("白名单放行");
            white = true;
        } else if(requestUrl.contains("/authorityCenter/login")) {
            log.info("登录放行");
            white = true;
        } else if(requestUrl.contains("/authorityCenter/register")) {
            log.info("注册放行");
            white = true;
        }
        String token = getToken(exchange);
        if (StringUtils.isBlank(token) && white) {
            return chain.filter(exchange);
        }
        if (StringUtils.isBlank(token)) {
            log.error("无效token");
            return Mono.error(InvalidTokenException.getInstance("无效token"));
        }

        if(reactor){
//            Mono<UserReqDTO> in = Mono.just(new UserReqDTO()).single();
//            Mono<KingHoodResponseVO<UserResDTO>> mono = oauthRemote.user(in);
//            KingHoodResponseVO<UserResDTO> res = mono.block();

            // 网关缓存5秒+随机数token数据
            UserResDTO user = null;
            String key = RedisConstant.TOKEN_PREFIX + token;
            String value = redisManager.keyValue().get(key);
            if(StringUtils.isNotBlank(value)){
                // 这里存在一个泛型，解析会报
                // class com.alibaba.fastjson.JSONObject cannot be cast to class com.zhang.infrastructure.model.dto.authority.UserResDTO
                // 所以reids不能保存res，应该保存具体的data
//                res = JSON.parseObject(value, KingHoodResponseVO.class);
                user = JSON.parseObject(value, UserResDTO.class);
            } else {
                KingHoodResponseVO<UserResDTO> res = authRemote.getUser(new GetUserReqDTO(token));
                if (!res.isSuccess()) {
                    log.error("token访问异常");
                    return Mono.error(InvalidTokenException.getInstance(res.getMessage()));
                }
                if(Objects.isNull(res.getData())){
                    log.error("无效token");
                    return Mono.error(InvalidTokenException.getInstance(res.getMessage()));
                }
                int expire = 4000 + ThreadLocalRandom.current().nextInt(1000);
                user = res.getData();
                redisManager.keyValue().set(key, JSON.toJSONString(user), expire);
            }

            if (Objects.isNull(user.getAuthority()) || CollectionUtils.isEmpty(user.getAuthority().getUrlList())
                    || !checkUrl(user.getAuthority().getUrlList(), requestUrl)) {
                log.error("{},非法访问", user);
                return Mono.error(InvalidTokenException.getInstance("非法访问"));
            }

            //将用户信息封装到请求头，也可以采用base64进行加密放一个请求头中，微服务采用过滤器进行解密
            ServerHttpRequest tokenRequest = exchange.getRequest().mutate()
                    .header(HeaderConstant.HEADER_USER_Id, user.getUser().getId().toString())
                    .header(HeaderConstant.HEADER_USER_NAME, user.getUser().getUsername())
                    .build();
            ServerWebExchange exchangeNew = exchange.mutate().request(tokenRequest).build();

            return chain.filter(exchangeNew);
        }

        String value = redisManager.keyValue().get(token);
        Map<String,Object> kingHoodPrincipal = JSON.parseObject(value, HashMap.class);
        if(Objects.isNull(kingHoodPrincipal)){
            log.error("无效token");
            return Mono.error(InvalidTokenException.getInstance("无效token"));
        }
        Object infoValue = kingHoodPrincipal.get("info");
        UserDTO info = JSON.parseObject(JSON.toJSONString(infoValue), UserDTO.class);
//        AInfo info = kingHoodPrincipal.getInfo();
//        List<String> authList = info.getAuthList();
//        if (!CollectionUtils.isEmpty(authList) && !checkUrl(authList, requestUrl)) {
//            log.error("{},非法访问", kingHoodPrincipal);
//            return Mono.error(KingHoodException.getInstance("非法访问"));
//        }
        if(Objects.equals(kingHoodPrincipal.get("issuer"),"inner") && info instanceof UserDTO){
            UserDTO userDTO = (UserDTO) info;
            //将用户信息封装到请求头，也可以采用base64进行加密放一个请求头中，微服务采用过滤器进行解密
            ServerHttpRequest tokenRequest = exchange.getRequest().mutate()
                    .header(HeaderConstant.HEADER_USER_Id, userDTO.getId().toString())
                    .header(HeaderConstant.HEADER_USER_NAME, userDTO.getUsername())
                    .build();
            ServerWebExchange exchangeNew = exchange.mutate().request(tokenRequest).build();
            return chain.filter(exchangeNew);
        }

        log.error("无效token");
        return Mono.error(InvalidTokenException.getInstance("无效token"));
    }

    /**
     * 这里需要考虑白名单的写法是/**
     * 这里不考虑*h号在中间的情况
     */
    private boolean checkUrl(List<String> ignoreUriList, String requestUrl) {
        for (String ignore : ignoreUriList) {
            if (ignore.equals(requestUrl)) {
                return true;
            }
            if (ignore.contains("*")) {
                String uri = ignore.substring(0, ignore.indexOf("*"));
                if (requestUrl.startsWith(uri)) {
                    return true;
                }
            }
        }
        return false;
    }

    private String getToken(ServerWebExchange exchange) {
        List<String> authorization = exchange.getRequest().getHeaders().get("Authorization");
        if (CollectionUtils.isEmpty(authorization)) {
            return null;
        }
        String bear = authorization.get(0);
        if (bear.contains("Bearer ")) {
            String token = bear.substring("Bearer ".length());
            log.info("token:{}", token);
            return token;
        }
        return null;
    }
}
